﻿using System;
using System.Web;
using System.Net;
using System.Linq;
using System.Web.Security;
using System.Globalization;
using Stimulus360.Models.Data;
using Stimulus360.Repositories;
using Microsoft.ServiceModel.Web;
using System.Collections.Generic;
using Stimulus360.Services.Interfaces;
using Stimulus360.Repositories.Interfaces;
using Stimulus360.Models.Data.CustomTypes;

namespace Stimulus360.Services
{
    /// <summary>
    ///     This class interacts with the repositories to get user related data
    /// </summary>
    public class UserService : IUserService
    {
        #region IUserProfileService Members

        /// <summary>
        ///     Instance of IUserRepository to be used
        /// </summary>
        private readonly IUserRepository _userRepository;

        /// <summary>
        ///     Instance of IDataRepository to be used
        /// </summary>
        private readonly IDataRepository _dataRepository;

        /// <summary>
        ///     Instance of IMembershipService to be used
        /// </summary>
        private readonly IMembershipService _membershipService;

        /// <summary>
        ///     Default constructor. Creates the necessary default instances 
        /// </summary>
        public UserService()
        {
            try
            {
                _userRepository = new UserRepository();
                _membershipService = new MembershipService();
                _dataRepository = new DataRepository();

            }
            catch (NullReferenceException exception)
            {

                // Exception conditions detected when invoking this service as a RESTFul service need to report an HTTP status code in the 
                //  400-500   range in addition to a message that details the exception
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "Could not connect to the database", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Dependency Injection enabled constructor. Allows to inject the various instances
        ///     to be used
        /// </summary>
        /// <param name="userRepository">
        ///     The IUserRepository Instance to be used
        /// </param>
        /// <param name="dataRepository">
        ///     The IDataRepository instance to be used
        /// </param>
        /// <param name="membershipService">
        ///     The Membership service to be used
        /// </param>
        public UserService(IUserRepository userRepository, IDataRepository dataRepository, IMembershipService membershipService)
        {
            try
            {
                _userRepository = userRepository ?? new UserRepository();
                _membershipService = membershipService ?? new MembershipService();
                _dataRepository = dataRepository ?? new DataRepository();

            }
            catch (NullReferenceException exception)
            {

                // Exception conditions detected when invoking this service as a RESTFul service need to report an HTTP status code in the 
                //  400-500   range in addition to a message that details the exception
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "Could not connect to the database", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception );
            }
        }

        /// <summary>
        ///     Method to get User image
        /// </summary>
        /// <param name="userName">
        ///     User whose thumbnail has to be retrieved.
        /// </param>
        /// <returns>
        ///     User Image as byte array
        /// </returns>
        public byte[] GetUserThumbnail(string userName)
        {
            try
            {
                userName = HttpUtility.HtmlEncode(userName);
                UserProfile userProfile = _userRepository.FindUserByName(userName);
                if (userProfile != null)
                {
                    if (userProfile.ThumbnailPhoto == null)
                        return null;

                    byte[] image = (userProfile.ThumbnailPhoto).ToArray();
                    return image;
                }
                throw new WebProtocolException(HttpStatusCode.NotFound, "No User found for username: " + userName, null);
            }
            catch (WebProtocolException)
            {
                throw;
            }
            catch (ArgumentException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "User name provided is invalid", exception);
            }

            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "The Database context was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///      Method to save User image
        /// </summary>
        /// <param name="userName">
        ///     User whose thumbnail has to be saved.
        /// </param>
        /// <param name="image">
        ///     User Photo to be saved.
        /// </param>
        public void SaveUserThumbnail(string userName, byte[] image)
        {
            try
            {
                userName = HttpUtility.HtmlEncode(userName);

                UserProfile userProfile = _userRepository.FindUserByName(userName);
                if (userProfile != null)
                {
                    userProfile.ThumbnailPhoto = image;
                    _userRepository.UpdateUser();
                }
                else
                {
                    throw new WebProtocolException(HttpStatusCode.NotFound, "No User found for username: " + userName,
                                                   null);
                }
            }
            catch (WebProtocolException)
            {
                throw;
            }
            catch (ArgumentException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "User name provided is invalid", exception);
            }

            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "The Database context was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Method to retrive the user by given user name
        /// </summary>
        /// <param name="userName">
        ///     User to be retrieved.
        /// </param>
        /// <returns>
        ///    The User
        /// </returns>
        public UserProfile FindByUserName(string userName)
        {
            try
            {
                userName = HttpUtility.HtmlEncode(userName);

                return _userRepository.FindUserByName(userName);
            }
            catch (ArgumentException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "User name provided is invalid", exception);
            }

            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "The Database context was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Method to create a new user
        /// </summary>
        /// <param name="user">
        ///     User to be created
        /// </param>
        /// <param name="interests">
        ///     The piped interests string
        /// </param>
        public MembershipCreateStatus Create(UserProfile user, string interests)
        {
            try
            {

                MembershipCreateStatus createStatus = _membershipService.CreateUser(user.UserName, user.Password, user.Email);
                if (createStatus == MembershipCreateStatus.Success)
                {
                    AddUserInterests(user, interests);
                    _userRepository.CreateUser(user);
                    // Increase the scores for the user's interest categories.
                    IncreaseInvestmentCategoryScores(_userRepository.FindUserByName(user.UserName).UserInterests.ToList());
                    _userRepository.UpdateUser();
                }
                return createStatus;

            }
            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "One or more objects was null", exception);
            }
            catch (ArithmeticException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "There was an error in updating the Investment Category scores", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Method extracts interests from the interests string and adds them to the user
        /// </summary>
        /// <param name="user">
        ///     The user to whom interests are to be added
        /// </param>
        /// <param name="interests">
        ///     The interest string.
        /// </param>
        private static void AddUserInterests(UserProfile user, string interests)
        {
            var interestCategories = string.IsNullOrEmpty(interests) ? null : interests.TrimEnd(',').Split('?').Distinct().ToArray();
            if (interestCategories != null && interestCategories.Length == 2)
            {
                var arrInterests = string.IsNullOrEmpty(interestCategories[0]) ? null : interestCategories[0].TrimEnd(',').Split(',').Distinct().ToArray();
                var arrInterestIds = string.IsNullOrEmpty(interestCategories[1]) ? null : interestCategories[1].TrimEnd(',').Split(',').Distinct().ToArray();
                if (arrInterestIds != null && arrInterests != null)
                {
                    for (int intLoop = 0; intLoop < arrInterests.Length; intLoop++)
                    {
                        UserInterest userInterest = new UserInterest();
                        userInterest.Category = arrInterests[intLoop];
                        userInterest.CategoryId = Convert.ToInt16(arrInterestIds[intLoop], CultureInfo.CurrentCulture);
                        userInterest.UserName = user.UserName;
                        user.UserInterests.Add(userInterest);
                    }
                }
            }
        }

        /// <summary>
        ///     Method to increase the User Interest Count and Scores for the investment Categories
        /// </summary>
        /// <param name="interests">
        ///     The Investment Categories of User interest.
        /// </param>
        private static void IncreaseInvestmentCategoryScores(IEnumerable<UserInterest> interests)
        {

            foreach (UserInterest interest in interests)
            {
                double score = interest.InvestmentCategory.Score.GetValueOrDefault(0);
                int userInterestCount = interest.InvestmentCategory.UserInterestCount.GetValueOrDefault(0);
                interest.InvestmentCategory.Score = score + 1;
                interest.InvestmentCategory.UserInterestCount = userInterestCount + 1;
            }

        }

        /// <summary>
        ///     Method to udpate the User
        /// </summary>
        /// <param name="user">
        ///     User to be updated
        /// </param>
        /// <param name="interests">
        ///     The piped interest string.
        /// </param>
        public void Update(UserProfile user, string interests)
        {
            try
            {
                UserProfile existingProfile = _userRepository.FindUserByName(user.UserName);
                if (existingProfile != null)
                {
                    DecreaseInvestmentCategoryScores(existingProfile.UserInterests.ToList());

                    AddUserInterests(user, interests);
                    user.DateLastUpdated = DateTime.Now;
                    _userRepository.UpdateUser(user);
                    existingProfile = _userRepository.FindUserByName(user.UserName);
                    IncreaseInvestmentCategoryScores(existingProfile.UserInterests.ToList());
                    _userRepository.UpdateUser();
                }
                else
                {
                    throw new WebProtocolException(HttpStatusCode.NotFound, "No User found for username: " + user, null);
                }
            }
            catch (WebProtocolException)
            {
                throw;
            }
            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "One or more objects was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }


        /// <summary>
        ///     Method to decrease the User Interest Count and Scores for the investment Categories
        /// </summary>
        /// <param name="interests">
        ///     The Investment Categories of User interest.
        /// </param>
        private void DecreaseInvestmentCategoryScores(IEnumerable<UserInterest> interests)
        {
                foreach (UserInterest interest in interests)
                {
                    double score = interest.InvestmentCategory.Score.GetValueOrDefault(0);
                    int userInterestCount = interest.InvestmentCategory.UserInterestCount.GetValueOrDefault(0);
                    interest.InvestmentCategory.Score = score - 1;
                    interest.InvestmentCategory.UserInterestCount = userInterestCount - 1;
                }
                _userRepository.UpdateUser();
        }

        /// <summary>
        ///     Method to retrieve a paginated  set of ideas submitted by the user
        /// </summary>
        /// <param name="userName">
        ///     The User under consideration
        /// </param>
        /// <param name="pageNo">
        ///     The Page number being displayed
        /// </param>
        /// <param name="pageSize">
        ///     The number of ideas shown on a page
        /// </param>
        /// <returns>
        ///     Paginated  set of ideas submitted by the user
        /// </returns>
        /// <remarks>
        ///     All parameters have been taken in string format to enable REST calls in the future
        /// </remarks>
        public PaginatedCollection<Idea> GetSubmittedIdeas(string userName, string pageNo, string pageSize)
        {
            try
            {
                userName = HttpUtility.HtmlEncode(userName);
                pageNo = HttpUtility.HtmlEncode(pageNo);
                pageSize = HttpUtility.HtmlEncode(pageSize);

                var ideaList = _dataRepository.GetSubmittedIdeas(userName);
                return new PaginatedCollection<Idea>(ideaList, pageNo.ToInt(), pageSize.ToInt());

            }
            catch (ArgumentNullException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are null", exception);
            }
            catch (FormatException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are in invalid format",
                                               exception);
            }


            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "One or more objects was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Method to retrieve a paginated  set of user's favorite ideas
        /// </summary>
        /// <param name="userName">
        ///     The User under consideration
        /// </param>
        /// <param name="pageNo">
        ///     The Page number being displayed
        /// </param>
        /// <param name="pageSize">
        ///     The number of ideas shown on a page
        /// </param>
        /// <returns>
        ///     Paginated  set of User's favorite ideas
        /// </returns>
        /// <remarks>
        ///     All parameters have been taken in string format to enable REST calls in the future
        /// </remarks>
        public PaginatedCollection<Idea> GetFavoriteIdeas(string userName, string pageNo, string pageSize)
        {
            try
            {
                userName = HttpUtility.HtmlEncode(userName);
                pageNo = HttpUtility.HtmlEncode(pageNo);
                pageSize = HttpUtility.HtmlEncode(pageSize);

                var ideaList = _dataRepository.GetFavoriteIdeas(userName);
                return new PaginatedCollection<Idea>(ideaList, pageNo.ToInt(), pageSize.ToInt());
            }
            catch (ArgumentNullException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are null", exception);
            }
            catch (FormatException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are in invalid format",
                                               exception);
            }

            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError , "The Database context was null", exception );
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Method to retrieve a paginated  set of ideas recommended by the system based
        ///     on his interests and activities
        /// </summary>
        /// <param name="userName">
        ///     The User under consideration
        /// </param>
        /// <param name="pageNo">
        ///     The Page number being displayed
        /// </param>
        /// <param name="pageSize">
        ///     The number of projects shown on a page
        /// </param>
        /// <returns>
        ///     Paginated  set of ideas recommended by the system based
        ///     on his interests and activities
        /// </returns>
        /// <remarks>
        ///     All parameters have been taken in string format to enable REST calls in the future
        /// </remarks>
        public PaginatedCollection<Idea> GetRecommendedIdeas(string userName, string pageNo, string pageSize)
        {
            try
            {
                userName = HttpUtility.HtmlEncode(userName);
                pageNo = HttpUtility.HtmlEncode(pageNo);
                pageSize = HttpUtility.HtmlEncode(pageSize);

                var ideaList = _dataRepository.GetRecommendedIdeas(userName);
                return new PaginatedCollection<Idea>(ideaList, pageNo.ToInt(), pageSize.ToInt());

            }
            catch (ArgumentNullException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are null", exception);
            }
            catch (FormatException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are in invalid format",
                                               exception);
            }

            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "The Database context was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Method to retrieve a paginated  set of ideas recommended by the system based
        ///     on his interests and activities
        /// </summary>
        /// <param name="userName">
        ///     The User under consideration
        /// </param>
        /// <param name="page">
        ///     The Page number being displayed
        /// </param>
        /// <param name="pageSize">
        ///     The number of ideas shown on a page
        /// </param>
        /// <returns>
        ///     Paginated  set of ideas recommended by the system based
        ///     on his interests and activities
        /// </returns>
        /// <remarks>
        ///     All parameters have been taken in string format to enable REST calls in the future
        /// </remarks>
        public PaginatedCollection<Project> GetVolunteeredProjects(string userName, string page, string pageSize)
        {
            try
            {
                userName = HttpUtility.HtmlEncode(userName);
                page = HttpUtility.HtmlEncode(page);
                pageSize = HttpUtility.HtmlEncode(pageSize);

                var projList = _dataRepository.GetVolunteeredProjects(userName);
                return new PaginatedCollection<Project>(projList, page.ToInt(), pageSize.ToInt());
            }
            catch (ArgumentNullException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are null", exception);
            }
            catch (FormatException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are in invalid format",
                                               exception);
            }

            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "The Database context was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Method to retrieve a paginated  set of user's favorite projects
        /// </summary>
        /// <param name="userName">
        ///     The User under consideration
        /// </param>
        /// <param name="page">
        ///     The Page number being displayed
        /// </param>
        /// <param name="pageSize">
        ///     The number of projects shown on a page
        /// </param>
        /// <returns>
        ///     Paginated  set of User's favorite projects
        /// </returns>
        /// <remarks>
        ///     All parameters have been taken in string format to enable REST calls in the future
        /// </remarks>
        public PaginatedCollection<Project> GetFavoriteProjects(string userName, string page, string pageSize)
        {
            try
            {
                userName = HttpUtility.HtmlEncode(userName);
                page = HttpUtility.HtmlEncode(page);
                pageSize = HttpUtility.HtmlEncode(pageSize);

                var projList = _dataRepository.GetFavoriteProjects(userName);
                return new PaginatedCollection<Project>(projList, page.ToInt(), pageSize.ToInt());

            }
            catch (ArgumentNullException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are null", exception);
            }
            catch (FormatException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are in invalid format",
                                               exception);
            }

            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "The Database context was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Method to retrieve a paginated  set of projects recommended by the system based
        ///     on his interests and activities
        /// </summary>
        /// <param name="userName">
        ///     The User under consideration
        /// </param>
        /// <param name="page">
        ///     The Page number being displayed
        /// </param>
        /// <param name="pageSize">
        ///     The number of projects shown on a page
        /// </param>
        /// <returns>
        ///     Paginated  set of projects recommended by the system based
        ///     on his interests and activities
        /// </returns>
        /// <remarks>
        ///     All parameters have been taken in string format to enable REST calls in the future
        /// </remarks>
        public PaginatedCollection<Project> GetRecommendedProjects(string userName, string page, string pageSize)
        {
            try

            {
                userName = HttpUtility.HtmlEncode(userName);
                page = HttpUtility.HtmlEncode(page);
                pageSize = HttpUtility.HtmlEncode(pageSize);

                var projList = _dataRepository.GetRecommendedProjects(userName);
                return new PaginatedCollection<Project>(projList, page.ToInt(), pageSize.ToInt());

            }
            catch (ArgumentNullException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are null", exception);
            }
            catch (FormatException exception)
            {
                throw new WebProtocolException(HttpStatusCode.BadRequest, "One or more arguments provided are in invalid format",
                                               exception);
            }

            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "The Database context was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        /// <summary>
        ///     Method to get a list of all the InvestmentCategorySummaries
        /// </summary>
        /// <returns>
        ///     List of all InvestmentCategorySummaries
        /// </returns>
        public ICollection<InvestmentCategorySummary> GetAllInvestmentCategories()
        {
            try
            {
                return _dataRepository.GetAllInvestmentCategorySummaries();
            }
            catch (NullReferenceException exception)
            {
                throw new WebProtocolException(HttpStatusCode.InternalServerError, "The Database context was null", exception);
            }
            catch (Exception exception)
            {
                throw new WebProtocolException(ExceptionHelper.GetStatusCode(exception), exception.Message, exception);
            }
        }

        #endregion

    }
}
